﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using static MovementAndRotate;

[Serializable]
public class MovementAndRotateBehaviour : PlayableBehaviour, IJumpableBehaviour
{
    #region Properties
    [Header("Movement")]
    public ActionType MovementType = ActionType.Self;
    public bool MovementEnable = true;
    public Vector3 MovementDirection = Vector3.up;
    public float MovementDistance = 0f;
    public Space MovementSpaceMode = Space.Self;

    [Header("Rotate")]
    public ActionType RotateType = ActionType.Self;
    public bool RotateEnable = true;
    public Vector3 RotateDirection = Vector3.up;
    public float RotateTotalAngle = 360f;
    public Space RotateSpaceMode = Space.Self;
    #endregion

    #region Self
    private Transform target;
    private MovementAndRotateProperty SelfProperty;
    private Dictionary<Transform, MovementAndRotateProperty> mrProperties;
    #endregion

    #region Func
    public void Init(Transform target)
    {
        if (null == target)
        {
            return;
        }

        //Debug.LogFormat("Init: Target={4}, Start={0}, End={1}, Track={2}, Direcotr={3}, PlayableAsset = {5}",
        //    TimeInfo.start, TimeInfo.end, OwnerTrack.name, Director.name, target.name, PlayableAsset.name);

        this.target = target;

        SaveTransformValue();

        //Jumpable
        JumpabelManager.AddPlayableAsset(Director, OwnerTrack, this);
    }

    void SaveTransformValue()
    {
        SelfProperty = new MovementAndRotateProperty();
        mrProperties = new Dictionary<Transform, MovementAndRotateProperty>();

        SaveProperty(target, SelfProperty);

        int count = target.childCount;
        for (int i = 0; i < count; i++)
        {
            SaveProperty(target.GetChild(i), new MovementAndRotateProperty());
        }
    }

    void SaveProperty(Transform t, MovementAndRotateProperty item)
    {
        //Movement Destination
        item.originLocalPosition = target.localPosition;
        item.originPosition = target.position;

        item.destinationLocalPosition = item.originLocalPosition + (MovementDirection * MovementDistance);
        item.destinationPosition = item.originPosition + (MovementDirection * MovementDistance);

        //Rotate Destination
        item.originLocalEulerAngles = target.localEulerAngles;
        item.originEulerAngles = target.eulerAngles;

        item.destinationLocalEulerAngles = item.originLocalEulerAngles + (RotateDirection * RotateTotalAngle);
        item.destinationEulerAngles = item.originEulerAngles + (RotateDirection * RotateTotalAngle);

        //Save to Dict
        mrProperties.Add(t, item);
    }

    void DoMovement(double current, double duration)
    {
        if (MovementType == ActionType.Self)
        {
            MovementTransform(current, duration, target);
        }
        else
        {
            int count = target.childCount;
            for (int i = 0; i < count; i++)
            {
                MovementTransform(current, duration, target.GetChild(i));
            }
        }
    }

    void MovementTransform(double current, double duration, Transform t)
    {
        if (!mrProperties.ContainsKey(t))
        {
            return;
        }

        MovementAndRotateProperty item = mrProperties[t];

        if (MovementSpaceMode == Space.Self)
        {
            t.localPosition = Vector3.Lerp(item.originLocalPosition, item.destinationLocalPosition, 
                (float)(current / duration));
        }
        else
        {
            t.position = Vector3.Lerp(item.originPosition, item.destinationPosition, 
                (float)(current / duration));
        }
    }

    void DoRotate(double current, double duration)
    {
        if (RotateType == ActionType.Self)
        {
            RotateTransform(current, duration, target);
        }
        else
        {
            int count = target.childCount;
            for (int i = 0; i < count; i++)
            {
                RotateTransform(current, duration, target.GetChild(i));
            }
        }
    }

    void RotateTransform(double current, double duration, Transform t)
    {
        if (!mrProperties.ContainsKey(t))
        {
            return;
        }

        MovementAndRotateProperty item = mrProperties[t];

        if (RotateSpaceMode == Space.Self)
        {
            t.localEulerAngles = Vector3.Lerp(item.originLocalEulerAngles, item.destinationLocalEulerAngles, 
                (float)(current / duration));
        }
        else
        {
            t.eulerAngles = Vector3.Lerp(item.originEulerAngles, item.destinationEulerAngles, 
                (float)(current / duration));
        }
    }

    void Process(double current, double duration)
    {
        if (null == target)
        {
            return;
        }

        if (MovementEnable)
        {
            DoMovement(current, duration);
        }

        if (RotateEnable)
        {
            DoRotate(current, duration);
        }
    }
    #endregion

    #region PlayableBehaviour
    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        Process(playable.GetTime(), playable.GetDuration());
    }

    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
    }

    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
    }
    #endregion

    #region IJumpableBehaviour
    public PlayableDirector Director { get; set; }
    public PlayableAsset PlayableAsset { get; set; }
    public TimeInfo TimeInfo { get; set; }
    public TrackAsset OwnerTrack { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public void OnDirectorJump(bool forward, double originTime, double destinationTime)
    {
        double current = 0;
        double duration = TimeInfo.end - TimeInfo.start;

        if (forward)
        {
            if (TimeInfo.end < destinationTime)
            {
                current = duration;
            }
            else
            {
                current = duration - (TimeInfo.end - destinationTime);
            }
        }
        else
        {
            if (TimeInfo.start > destinationTime)
            {
                current = 0;
            }
            else
            {
                current = duration - (TimeInfo.end - destinationTime);
            }
        }

        Process(current, duration);
    }
    #endregion

}
